package com.liming.coupon.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liming.common.constant.SystemConstants;
import com.liming.common.exception.BusinessException;
import com.liming.common.pojo.dto.coupon.*;
import com.liming.common.pojo.dto.order.user.mini.MiniOrderProductDTO;
import com.liming.common.pojo.dto.order.user.mini.MiniOrderSelectCouponDTO;
import com.liming.common.pojo.entity.BaseEntity;
import com.liming.common.pojo.entity.coupon.Coupon;
import com.liming.common.pojo.entity.coupon.UserCoupon;
import com.liming.common.pojo.vo.PageVO;
import com.liming.common.pojo.vo.coupon.openfeign.*;
import com.liming.common.pojo.vo.order.openfeign.mini.FeignMiniOrderProductVO;
import com.liming.common.pojo.vo.order.openfeign.mini.FeignMiniOrderSelectCouponVO;
import com.liming.common.util.BeanUtils;
import com.liming.common.util.PageUtils;
import com.liming.common.util.ThreadLocalUtils;
import com.liming.common.util.mybatisplus.MPUtil;
import com.liming.coupon.enums.CouponAvailableEnum;
import com.liming.coupon.enums.CouponTypeEnum;
import com.liming.coupon.enums.CouponUseStatusEnum;
import com.liming.coupon.mapper.CouponMircoMapper;
import com.liming.coupon.service.CouponMircoService;
import com.liming.coupon.service.UserCouponMircoService;
import com.liming.product.client.ProductClient;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.liming.common.constant.DBConstants.COUPON_UNUSED;
import static com.liming.common.constant.DBConstants.COUPON_USAGE_UNLIMITED;

/**
 * @author liming
 * @date 2025/1/18
 */
@Slf4j
@Service
@AllArgsConstructor
public class CouponMircoServiceImpl extends ServiceImpl<CouponMircoMapper, Coupon> implements CouponMircoService, MPUtil<Coupon> {

    private final UserCouponMircoService userCouponMircoService;
    private final ProductClient productClient;

    @Override
    public PageVO<FeignCouponVO> pageCoupon(CouponPageQuery query) {
        IPage<FeignCouponVO> convert = lambdaQuery()
                .like(StrUtil.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .eq(ObjectUtil.isNotNull(query.getDiscountType()), Coupon::getDiscountType, query.getDiscountType())
                .eq(ObjectUtil.isNotNull(query.getAvailable()), Coupon::getAvailable, query.getAvailable())
                .orderByDesc(BaseEntity::getCreateTime)
                .page(setPage(query))
                .convert(coupon -> {
                    FeignCouponVO vo = new FeignCouponVO();
                    org.springframework.beans.BeanUtils.copyProperties(coupon, vo);
                    return vo;
                });
        PageVO<FeignCouponVO> vo = new PageVO<>();
        org.springframework.beans.BeanUtils.copyProperties(convert, vo);
        return vo;
    }

    @Override
    public FeignCouponVO getCouponById(Long id) {
        Coupon coupon = getById(id);
        Assert.notNull(coupon, () -> new BusinessException("优惠券不存在"));
        FeignCouponVO vo = new FeignCouponVO();
        org.springframework.beans.BeanUtils.copyProperties(coupon, vo);
        return vo;
    }

    @Override
    public Boolean add(AddCouponDTO dto) {
        Coupon coupon = new Coupon();
        org.springframework.beans.BeanUtils.copyProperties(dto, coupon);
        // 优惠券名称不能相同
        checkNameUnique(coupon, null);
        // 新增优惠券时：全平台使用次数为0次
        coupon.setUsageCount(0);
        // 新增优惠券时：默认不可用
        coupon.setAvailable(0);
        Assert.isTrue(save(coupon), () -> new BusinessException("优惠券保存失败"));
        return true;
    }

    private void checkNameUnique(Coupon coupon, Long id) {
        Coupon check = lambdaQuery().eq(StrUtil.isNotBlank(coupon.getName()), Coupon::getName, coupon.getName()).ne(ObjectUtil.isNotNull(id), Coupon::getId, id).one();
        Assert.isNull(check, () -> new BusinessException("优惠券名称已存在"));
    }

    @Override
    public Boolean updAvailable(Long id) {
        Coupon coupon = getById(id);
        Assert.notNull(coupon, () -> new BusinessException("优惠券不存在"));
        // 修改优惠券可用状态
        Integer available = ObjectUtil.equal(coupon.getAvailable(), CouponAvailableEnum.DISABLED.getCode()) ? CouponAvailableEnum.ENABLED.getCode() : CouponAvailableEnum.DISABLED.getCode();
        coupon.setAvailable(available);
        Assert.isTrue(updateById(coupon), () -> new BusinessException("优惠券状态更新失败"));
        return true;
    }

    @Override
    public Boolean updateCoupon(UpdateCouponDTO updateCouponDTO) {
        Coupon update = new Coupon();
        org.springframework.beans.BeanUtils.copyProperties(updateCouponDTO, update);
        checkNameUnique(update, updateCouponDTO.getId());
        Assert.isTrue(updateById(update));
        return true;
    }

    @Override
    public Boolean removeCoupon(Long id) {
        Assert.isTrue(removeById(id), () -> new BusinessException("优惠券删除失败"));
        return true;
    }

    @Override
    public Map<Long, Coupon> enableCouponMapByIds(List<Long> ids) {
        return lambdaQuery().in(CollUtil.isNotEmpty(ids), Coupon::getId, ids).eq(Coupon::getAvailable, CouponAvailableEnum.ENABLED.getCode()).list().stream().collect(Collectors.toMap(BaseEntity::getId, Function.identity()));
    }

    @Override
    public Boolean get(Long id) {
        Coupon coupon = getById(id);
        Assert.notNull(coupon, () -> new BusinessException("优惠券不存在"));
        // 用户领取次数 与 优惠券最大使用次数 判断
        String userId = ThreadLocalUtils.get(SystemConstants.ID);
        Integer usageLimit = coupon.getUsageLimit();
        // 若优惠券使用次数 有限制 - 才需要判断
        if (ObjectUtil.notEqual(usageLimit, COUPON_USAGE_UNLIMITED)) {
            long userCouponCount = userCouponMircoService.countByUserIdAndCouponId(id);
            Assert.isTrue(userCouponCount < usageLimit, () -> new BusinessException("您领取的优惠券数量已达上限"));
        }
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(Long.parseLong(userId));
        userCoupon.setCouponId(id);
        userCoupon.setIsUsed(COUPON_UNUSED);
        userCoupon.setEndDate(coupon.getEndDate());
        Assert.isTrue(userCouponMircoService.save(userCoupon), () -> new BusinessException("优惠券领取失败"));
        return true;
    }

    @Override
    public Coupon checkAvailable(CouponValidDTO dto) {
        Coupon coupon = getById(dto.getId());
        // 优惠券 存在
        Assert.notNull(coupon, () -> new BusinessException("优惠券不存在"));
        // 优惠券 可用
        Assert.equals(coupon.getAvailable(), CouponAvailableEnum.ENABLED.getCode(), () -> new BusinessException("优惠券不可用"));
        // 优惠券 最低消费金额 满足
        Integer amount = dto.getTotalAmount();
        Assert.isTrue(amount >= coupon.getMinPurchaseAmount(), () -> new BusinessException("优惠券最低消费金额不满足"));
        // 优惠券 使用时间段
        Assert.isTrue(coupon.getStartDate().isBefore(LocalDateTime.now()) && coupon.getEndDate().isAfter(LocalDateTime.now()), () -> new BusinessException("优惠券未在使用期间内"));
        // 用户优惠券 使用次数
        Map<Long, Long> couponIdAndUseFrequencyMap = userCouponMircoService.userCouponUseFrequency();
        Integer usageLimit = coupon.getUsageLimit();
        Assert.isTrue(couponIdAndUseFrequencyMap.getOrDefault(coupon.getId(), 0L) < usageLimit, () -> new BusinessException("优惠券使用次数已达上限"));
        return coupon;
    }

    @Override
    public Map<Long, List<FeignMiniCouponVO>> productCanUseCoupons(Map<Long, Integer> productIdAndPriceMap) {
        if (CollUtil.isEmpty(productIdAndPriceMap)) {
            return Map.of();
        }
        Set<Map.Entry<Long, Integer>> productIdAndPriceEntrySet = productIdAndPriceMap.entrySet();
        Map<Long, List<FeignMiniCouponVO>> resultMap = new HashMap<>();
        LocalDateTime now = LocalDateTime.now();
        // 查询目前可用的优惠券 Map<优惠券类型, Map<优惠券最低消费金额, 优惠券列表>>
        Map<Integer, List<Coupon>> minPurchaseAmountAndCoupons = lambdaQuery().le(Coupon::getStartDate, now).ge(Coupon::getEndDate, now).eq(Coupon::getAvailable, CouponAvailableEnum.ENABLED.getCode()).list().stream().collect(Collectors.groupingBy(Coupon::getMinPurchaseAmount));
        // 优惠券类型为满减券：Map<优惠券最低使用金额，优惠券列表>
        for (Map.Entry<Long, Integer> productIdAndPriceEntry : productIdAndPriceEntrySet) {
            Long productId = productIdAndPriceEntry.getKey();
            Integer productPrice = productIdAndPriceEntry.getValue();
            // 使用 computeIfAbsent 初始化或获取 MiniCouponVO 列表
            List<FeignMiniCouponVO> miniCouponVOList = resultMap.computeIfAbsent(productId, k -> new ArrayList<>());
            // 过滤出满足条件的优惠券并添加到 miniCouponVOList 中
            for (Map.Entry<Integer, List<Coupon>> entry : minPurchaseAmountAndCoupons.entrySet()) {
                Integer minPurchaseAmount = entry.getKey();
                List<Coupon> coupons = entry.getValue();
                if (productPrice >= minPurchaseAmount) {
                    for (Coupon coupon : coupons) {
                        FeignMiniCouponVO miniCouponVO = new FeignMiniCouponVO();
                        org.springframework.beans.BeanUtils.copyProperties(coupon, miniCouponVO);
                        miniCouponVOList.add(miniCouponVO);
                    }
                }
            }
            // 查询默认使用优惠券
            miniCouponVOList.stream().max(Comparator.comparing(FeignMiniCouponVO::getDiscountAmount)).ifPresent(miniCouponVO -> miniCouponVO.setDefaultUse(true));
        }
        return resultMap;
    }

    @Override
    public List<FeignMiniProductUserCouponVO> couponDetailList(List<Long> couponIds) {
        Map<Long, Coupon> idAndCouponMap = enableCouponMapByIds(couponIds);
        // 用户领取优惠券次数 Map<优惠券id, 用户领取次数>
        Map<Long, Long> userCouponGetFrequency = userCouponMircoService.userCouponGetFrequency();
        List<FeignMiniProductUserCouponVO> result = new ArrayList<>();
        for (Long couponId : couponIds) {
            // 用户领取优惠券次数
            Long userGetFrequency = userCouponGetFrequency.getOrDefault(couponId, 0L);
            // 获取优惠券可领取次数
            Coupon coupon = idAndCouponMap.get(couponId);
            Integer usageLimit = coupon.getUsageLimit();
            FeignMiniProductUserCouponVO vo = BeanUtils.toBean(coupon, FeignMiniProductUserCouponVO.class);
            vo.setCanReceiveQuantity(Math.max(usageLimit - userGetFrequency, 0));
            if (ObjectUtil.equal(usageLimit, COUPON_USAGE_UNLIMITED)) {
                vo.setCanReceiveQuantity(Long.MAX_VALUE);
            }
            result.add(vo);
        }
        return result;
    }

    @Override
    public List<FeignMiniCouponVO> userHaveCoupons(Long userId) {
        if (ObjectUtil.isNotNull(userId)) {
            ThreadLocalUtils.set(SystemConstants.ID, userId.toString());
        }
        List<Long> couponIds = userCouponMircoService.userHaveCouponIds();
        Map<Long, Coupon> couponMap = enableCouponMapByIds(couponIds);
        // 只保留 平台设置可用的优惠券
        couponIds = availableCouponIds(couponMap, couponIds);
        if (CollUtil.isEmpty(couponMap)) {
            return Collections.emptyList();
        }
        return couponIds.stream().map(id -> {
            Coupon coupon = couponMap.get(id);
            return BeanUtils.toBean(coupon, FeignMiniCouponVO.class);
        }).toList();
    }

    @Override
    public PageVO<FeignMiniUserHomeCouponVO> userHomeCouponPage(UserHomeCouponPageQuery query) {
        IPage<Long> page = userCouponMircoService.userHaveCouponIdsPage(query);
        Map<Long, Coupon> idAndCoupon = enableCouponMapByIds(page.getRecords());
        page.setRecords(availableCouponIds(idAndCoupon, page.getRecords()));
        if (CollUtil.isEmpty(idAndCoupon)) {
            return PageUtils.emptyPage(query);
        }
        return PageUtils.convert(page, id -> {
            Coupon coupon = idAndCoupon.get(id);
            FeignMiniUserHomeCouponVO vo = BeanUtils.toBean(coupon, FeignMiniUserHomeCouponVO.class);
            vo.setDescription("全场通用");
            return vo;
        }, v -> LocalDateTime.now().isAfter(v.getStartDate()) && LocalDateTime.now().isBefore(v.getEndDate()));
    }

    @Override
    public List<FeignMiniCanUseCouponVO> canUseCouponsByAmount(Integer amount) {
        if (ObjectUtil.isNull(amount)) {
            return Collections.emptyList();
        }
        int trueAmount = amount * 100;
        List<Long> couponIds = userCouponMircoService.userHaveCouponIds();
        Map<Long, Coupon> idAndCoupon = enableCouponMapByIds(couponIds);
        // 只保留 平台设置可用的优惠券
        couponIds = availableCouponIds(idAndCoupon, couponIds);
        if (CollUtil.isEmpty(idAndCoupon)) {
            return Collections.emptyList();
        }

        Map<FeignMiniCanUseCouponVO, Integer> couponCountMap = new HashMap<>();

        List<FeignMiniCanUseCouponVO> canUseCoupons = couponIds.stream().map(id -> {
                    Coupon coupon = idAndCoupon.get(id);
                    FeignMiniCanUseCouponVO vo = BeanUtils.toBean(coupon, FeignMiniCanUseCouponVO.class);
                    vo.setDescription("全场通用");
                    return vo;
                })
                .filter(vo -> (LocalDateTime.now().isAfter(vo.getStartDate()) && LocalDateTime.now().isBefore(vo.getEndDate())))
                .filter(vo -> trueAmount >= vo.getMinPurchaseAmount())
                .toList();

        for (FeignMiniCanUseCouponVO canUseCoupon : canUseCoupons) {
            couponCountMap.compute(canUseCoupon, (k, v) -> v == null ? 1 : v + 1);
        }
        List<FeignMiniCanUseCouponVO> result = new ArrayList<>();
        for (Map.Entry<FeignMiniCanUseCouponVO, Integer> entry : couponCountMap.entrySet()) {
            FeignMiniCanUseCouponVO vo = entry.getKey();
            vo.setSameCouponCount(entry.getValue());
            result.add(vo);
        }
        return result;
    }

    @Override
    public List<FeignMiniCannotUseCouponVO> cannotUseCouponsByAmount(Integer amount) {
        if (ObjectUtil.isNull(amount)) {
            return Collections.emptyList();
        }
        int trueAmount = amount * 100;
        List<Long> couponIds = userCouponMircoService.userHaveCouponIds();
        Map<Long, Coupon> idAndCoupon = enableCouponMapByIds(couponIds);
        // 只保留 平台设置可用的优惠券
        couponIds = availableCouponIds(idAndCoupon, couponIds);
        if (CollUtil.isEmpty(idAndCoupon)) {
            return Collections.emptyList();
        }
        List<FeignMiniCannotUseCouponVO> rawCoupons = couponIds.stream().map(id -> {
            Coupon coupon = idAndCoupon.get(id);
            FeignMiniCannotUseCouponVO vo = BeanUtils.toBean(coupon, FeignMiniCannotUseCouponVO.class);
            vo.setDescription("全场通用");
            return vo;
        }).toList();
        List<FeignMiniCannotUseCouponVO> result = rawCoupons
                .stream()
                .filter(vo -> vo.getEndDate().isBefore(LocalDateTime.now()) || vo.getStartDate().isAfter(LocalDateTime.now()) || trueAmount < vo.getMinPurchaseAmount())
                .toList();
        result.forEach(vo -> {
            if (trueAmount < vo.getMinPurchaseAmount()) {
                vo.setReason("不满足使用金额");
            } else if (vo.getEndDate().isBefore(LocalDateTime.now()) || vo.getStartDate().isAfter(LocalDateTime.now())) {
                vo.setReason("不在使用期限内");
            } else {
                vo.setReason("不能使用");
            }
        });
        return result;
    }

    private List<Long> availableCouponIds(Map<Long, Coupon> idAndCoupon, List<Long> couponIds) {
        return couponIds.stream().filter(id -> {
            Coupon coupon = idAndCoupon.get(id);
            if (ObjectUtil.isNull(coupon)) {
                return false;
            }
            return coupon.getAvailable() == 1;
        }).toList();
    }

    @Override
    public FeignMiniOrderSelectCouponVO miniOrderSelectCoupon(MiniOrderSelectCouponDTO dto) {
        Integer orderAmount = dto.getAmount();
        Long couponId = dto.getCouponId();
        List<MiniOrderProductDTO> productIdAndCountList = dto.getProductList();
        // 检查优惠券是否可以使用
        Coupon coupon = checkAvailable(new CouponValidDTO(couponId, orderAmount));
        FeignMiniOrderSelectCouponVO vo = new FeignMiniOrderSelectCouponVO();
        BigDecimal afterCouponOrderAmount;
        // 总金额折扣
        Integer discountType = coupon.getDiscountType();
        if (ObjectUtil.equal(discountType, CouponTypeEnum.FULL_REDUCTION.getCode())) {
            // 满减券
            afterCouponOrderAmount = new BigDecimal(orderAmount - coupon.getDiscountAmount()).movePointLeft(2);
        } else {
            // 折扣券
            afterCouponOrderAmount = BigDecimal.valueOf(coupon.getDiscountAmount()).movePointLeft(3).multiply(BigDecimal.valueOf(orderAmount).movePointLeft(2));
        }
        // Map<商品ID， 价格>
        Map<Long, Integer> productIdAndPriceMap = productClient.idAndPriceMap(productIdAndCountList.stream().map(MiniOrderProductDTO::getId).toList());
        List<FeignMiniOrderProductVO> list = BeanUtils.toBean(productIdAndCountList, FeignMiniOrderProductVO.class);
        // 计算商品单价
        for (FeignMiniOrderProductVO product : list) {
            Integer price = productIdAndPriceMap.get(product.getId());
            // 计算商品价格占比
            BigDecimal rate = BigDecimal.valueOf(price).divide(BigDecimal.valueOf(orderAmount), 2, RoundingMode.HALF_UP);
            product.setAfterCouponPrice(rate.multiply(afterCouponOrderAmount).setScale(2, RoundingMode.HALF_UP));
        }
        vo.setAfterCouponOrderAmount(afterCouponOrderAmount);
        vo.setProductList(list);
        return vo;
    }

    @Override
    public Boolean userOrderUseCoupon(Long couponId) {
        boolean flag = lambdaUpdate()
                .eq(BaseEntity::getId, couponId)
                .setSql("usage_count = usage_count + 1")
                .update();
        Assert.isTrue(flag, () -> new BusinessException("使用优惠券失败"));
        boolean useCouponFlag = userCouponMircoService.userUseCoupon(couponId);
        Assert.isTrue(useCouponFlag, () -> new BusinessException("使用优惠券失败"));
        return true;
    }

    @Override
    public Boolean returnCoupon(Long userId, Long couponId) {
        List<UserCoupon> userUseCoupons = userCouponMircoService.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, couponId)
                .eq(UserCoupon::getIsUsed, CouponUseStatusEnum.USED.getCode())
                .orderByDesc(BaseEntity::getId)
                .list();
        UserCoupon userCoupon = userUseCoupons.get(0);
        UserCoupon update = new UserCoupon();
        update.setId(userCoupon.getId());
        update.setIsUsed(CouponUseStatusEnum.UNUSED.getCode());
        Assert.isTrue(userCouponMircoService.updateById(update), () -> new BusinessException("优惠券退还失败"));
        boolean updateCount = lambdaUpdate()
                .eq(BaseEntity::getId, couponId)
                .setSql("usage_count = usage_count - 1")
                .update();
        Assert.isTrue(updateCount, () -> new BusinessException("优惠券使用次数减少失败"));
        return true;
    }
}
